<!--
 * @Author: your name
 * @Date: 2021-08-16 13:43:47
 * @LastEditTime: 2021-08-16 13:44:20
 * @Description: 
-->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>堆排序</title>
</head>

<body>
  <script>
    function heapSort(arr) {
      // 初始化大顶堆，从第一个非叶子结点开始
      for (let i = Math.floor(arr.length / 2 - 1); i >= 0; i--) {
        shiftDown(arr, i, arr.length)
      }

      // 排序，每一次for循环找出一个当前最大值，数组长度减一
      for (let i = Math.floor(arr.length - 1); i > 0; i--) {
        swap(arr, 0, i) // 根节点与最后一个节点交换
        shiftDown(arr, 0, i) // 从根节点开始调整，并且最后一个结点已经为当
        // 前最大值，不需要再参与比较，所以第三个参数
        // 为 i，即比较到最后一个结点前一个即可
      }
      return arr
    }

    // 将 i 结点以下的堆整理为大顶堆，注意这一步实现的基础实际上是：
    // 假设 结点 i 以下的子堆已经是一个大顶堆，shiftDown函数实现的
    // 功能是实际上是：找到 结点 i 在包括结点 i 的堆中的正确位置。后面
    // 将写一个 for 循环，从第一个非叶子结点开始，对每一个非叶子结点
    // 都执行 shiftDown操作，所以就满足了结点 i 以下的子堆已经是一大
    //顶堆
    function shiftDown(arr, i, length) { // [5, 6, 1, 7, 3, 2, 8, 4]
      let temp = arr[i] // 当前父节点
      // j<length 的目的是对结点 i 以下的结点全部做顺序调整
      for (let j = 2 * i + 1; j < length; j = 2 * j + 1) {
        temp = arr[i] // 将 arr[i] 取出，整个过程相当于找到 arr[i] 应处于的位置
        if (j + 1 < length && arr[j] < arr[j + 1]) {
          j++ // 找到两个孩子中较大的一个，再与父节点比较
        }
        if (temp < arr[j]) {
          swap(arr, i, j) // 如果父节点小于子节点:交换；否则跳出
          i = j // 交换后，temp 的下标变为 j
        } else {
          break
        }
      }
    }

    // 交换两个节点
    function swap(A, i, j) {
      let temp = A[i]
      A[i] = A[j]
      A[j] = temp
    }

    console.log(heapSort([5, 6, 1, 7, 3, 2, 8, 4]))
  </script>
</body>

</html>